package com.atguigu.gmall.index.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.annotation.GmallCache;
import com.atguigu.gmall.index.service.IndexService;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.utils.DistributedLockUtil;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.security.Key;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class IndexServiceImpl implements IndexService {

    @Autowired
    private GmallPmsClient pmsClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLockUtil distributedLockUtil;

    @Autowired
    private RedissonClient redissonClient;

    private static final String KEY_PREFIX = "index:cates:";

    private static final String LOCK_PREFIX = "index:cates:lock";
    @Override
    public List<CategoryEntity> queryCategories() {
        //查询所有分类列表 父id为0
        //从缓存查询数据
        String json = redisTemplate.opsForValue().get(KEY_PREFIX + 0);
        if (StringUtils.isNotBlank(json)) {
            List<CategoryEntity> categoryEntities = JSON.parseArray(json, CategoryEntity.class);
            return categoryEntities;
        }

        //为了防止缓存击穿 给热点key加锁
        RLock fairLock = redissonClient.getFairLock(LOCK_PREFIX + 0);
        fairLock.lock();

        try {
            //本次进程获取到锁之后 将查询到的数据放入缓存 但是其他进程在等待获取锁 无法取消 再次查询缓存 存在则直接返回
            String json2 = redisTemplate.opsForValue().get(KEY_PREFIX + 0);
            if (StringUtils.isNotBlank(json2)) {
                List<CategoryEntity> categoryEntities = JSON.parseArray(json2, CategoryEntity.class);
                return categoryEntities;
            }
            ResponseVo<List<CategoryEntity>> infoByParentId = pmsClient.getInfoByParentId(0L);
            List<CategoryEntity> categoryEntities = infoByParentId.getData();
            //将数据放入缓存
            if (CollectionUtils.isEmpty(categoryEntities)) {
                //为了防止缓存穿透 为null的数据也放入缓存 时间不易过长
                redisTemplate.opsForValue().set(KEY_PREFIX + 0, JSON.toJSONString(categoryEntities), 5, TimeUnit.MINUTES);
            } else {
                //为了防止缓存雪崩 给过期时间添加随机值
                redisTemplate.opsForValue().set(KEY_PREFIX + 0, JSON.toJSONString(categoryEntities), 180 + new Random().nextInt(10), TimeUnit.DAYS);
            }
            return categoryEntities;
        } finally {
            //解锁
            fairLock.unlock();
        }
    }

    //自定义注解完成缓存与分布式锁
    @Override
    @GmallCache(prefix = KEY_PREFIX,lock = LOCK_PREFIX,timeout = 259200,random = 14400)
    public List<CategoryEntity> queryLevel23CategoriesByPid(Long pid) {
            ResponseVo<List<CategoryEntity>> listResponseVo = pmsClient.queryLevel23CategoriesByPid(pid);
            List<CategoryEntity> categoryEntities = listResponseVo.getData();
            return categoryEntities;
    }


    //侵入式 添加缓存与分布式锁
    public List<CategoryEntity> queryLevel23CategoriesByPid2(Long pid) {
        //先查询缓存的数据
        String json = redisTemplate.opsForValue().get(KEY_PREFIX + pid);
        if (StringUtils.isNotBlank(json)) {
            List<CategoryEntity> categoryEntities = JSON.parseArray(json, CategoryEntity.class);
            return categoryEntities;
        }

        //为了防止缓存击穿 加入分布式锁
        RLock lock = redissonClient.getLock(LOCK_PREFIX + pid);
        //加锁
        lock.lock();
        try {
            //当前进程在获取锁之后 将数据放入缓存 因为其他进程已经在等待获取锁 无法取消 再次查询缓存
            String json2 = redisTemplate.opsForValue().get(KEY_PREFIX + pid);
            if (StringUtils.isNotBlank(json2)) {
                List<CategoryEntity> categoryEntities = JSON.parseArray(json2, CategoryEntity.class);
                return categoryEntities;
            }
            ResponseVo<List<CategoryEntity>> listResponseVo = pmsClient.queryLevel23CategoriesByPid(pid);
            List<CategoryEntity> categoryEntities = listResponseVo.getData();
            //将数据放入缓存
            if (CollectionUtils.isEmpty(categoryEntities)) {
                //为了防止缓存穿透 为null的数据也放入缓存中 只缓存5秒
                redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 5, TimeUnit.MINUTES);
            } else {
                //为了防止缓存雪崩 给缓存时间添加随机值
                redisTemplate.opsForValue().set(KEY_PREFIX + pid, JSON.toJSONString(categoryEntities), 180 + new Random().nextInt(10), TimeUnit.DAYS);
            }
            return categoryEntities;
        } finally {
            //解锁
            lock.unlock();
        }
    }

    //测试分布式锁
    @Override
    public void testLock() {
        //使用redisson加锁
        RLock lock = redissonClient.getLock("lock");
        lock.lock();

        //执行业务代码
        try {
            String numberStr = redisTemplate.opsForValue().get("number");
            if (StringUtils.isBlank(numberStr)) {
                //设置key的值
                redisTemplate.opsForValue().set("number", "1");
                return;
            }
            int number = Integer.parseInt(numberStr);
            redisTemplate.opsForValue().set("number", String.valueOf(++number));
        } finally {
            //解锁
            lock.unlock();
        }
    }
    public void testLock1() {
        String uuid = UUID.randomUUID().toString();
        //加锁
        Boolean flag = distributedLockUtil.tryLock("lock", uuid, 30);

        if (flag){
            try {
                //执行业务代码
                String numberStr = redisTemplate.opsForValue().get("number");
                if (StringUtils.isBlank(numberStr)) {
                    //设置key的值
                    redisTemplate.opsForValue().set("number", "1");
                    return;
                }
                int number = Integer.parseInt(numberStr);
                redisTemplate.opsForValue().set("number", String.valueOf(++number));
                TimeUnit.SECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //解锁
                distributedLockUtil.unLock("lock",uuid);
            }
        }
    }

    public void testLock2() {
        //每一个请求(线程)生成一个唯一标识 确保不会误删 设置锁的过期时间
        String uuid = UUID.randomUUID().toString();
        //加锁
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.SECONDS);
        if (!flag){
            //重试 为了防止栈溢出 睡一会
            try {
                Thread.sleep(50);
                this.testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            //执行业务代码
            String numberStr = redisTemplate.opsForValue().get("number");
            if (StringUtils.isBlank(numberStr)){
                //设置key的值
                redisTemplate.opsForValue().set("number","1");
                return;
            }
            int number = Integer.parseInt(numberStr);
            redisTemplate.opsForValue().set("number",String.valueOf(++number));

            String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del', KEYS[1]) " +
                    "else " +
                    "   return 0 end";
            redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList("lock"),uuid);
            //释放锁 为了防止误删，先判断在删除 采用lua脚本
//            if (StringUtils.equals(uuid,redisTemplate.opsForValue().get("lock"))){
//                //确认是自己的锁 释放
//            redisTemplate.delete("lock");
//            }
        }
    }
}
